<template>
  <q-select ref="QxSelect" v-model="selected" :clearable="clearable" :input-debounce="debounce"
            :loading="loading || inLoading" :multiple="multiple" :option-disable="opt => formatDisable(opt)"
            :options="dicData" :placeholder="formatPlaceholder" :rules="formatRules"
            :virtual-scroll-slice-size="pageSize" behavior="menu" transition-hide="jump-up" transition-show="jump-down"
            use-input v-bind="$attrs" @clear="clearSelected" @filter="filterFn" @popup-hide="popupHide"
            @virtual-scroll="onScroll" @update:model-value="updateSelected">
    <template v-for="item in Object.keys(useSlots())" v-slot:[item]>
      <slot :name="item"></slot>
    </template>
    <template v-slot:selected>
      <qx-tooltip class="qx-select-selected">{{ formatLabels(selected, props, formatLabel) }}</qx-tooltip>
    </template>
    <template v-slot:option="{ itemProps, opt }">
      <q-item :class="getDisable(opt, props) ? 'bg-red-4' : ''" v-bind="itemProps">
        <q-item-section v-if="badge && opt.color" class="col-auto">
          <q-badge :color="opt.color" class="q-badge-dot" rounded></q-badge>
        </q-item-section>
        <q-item-section>
          <qx-tooltip>{{ formatLabels(opt, props, formatLabel) }}</qx-tooltip>
        </q-item-section>
      </q-item>
    </template>
    <template v-slot:no-option>
      <q-item class="text-italic justify-center">{{ $t('NoData') }}</q-item>
    </template>
  </q-select>
</template>

<script setup>
import { i18n } from 'boot/i18n'
import QxTooltip from 'components/Global/Tools/QxTooltip.vue'
import { getAPI, postAPI } from 'src/api'
import { deepClone, getTypeof, isEmpty } from 'src/utils/utils'
import { computed, nextTick, ref, toRef, useSlots, watch, watchPostEffect } from 'vue'
import { filterList, formatLabels, formatValues, getDisable, getValue, labelKey, selectUnique, valueKey } from './utils'

const prop = defineProps({
  type: String, rules: Array, props: Object, field: String, page: Boolean, badge: Boolean, dicUrl: String,
  options: Array, rowInfo: Object, dynamicParams: Object, addName: Boolean, loading: Boolean, change: Function,
  dicQuery: Object, keyWords: String, multiple: Boolean, isSearch: Boolean, dicMethod: String, placeholder: String,
  formatLabel: Function, nonValiValue: Boolean, modelValue: [String, Number, Array],
  clearable: { type: Boolean, default: true }
})

const emits = defineEmits(['change', 'update:modelValue'])

const dicData = ref([])
const debounce = ref(50)
const QxSelect = ref(null)
const isAjax = ref(!!prop.dicUrl)
const inLoading = ref(false)
const selected = ref(void 0)
const ajaxChange = ref(false)
const formInfo = toRef(prop, 'rowInfo')
const totalsList = ref(prop.options || [])

const values = computed(() => formatValues(selected.value, prop.props))
const API = computed(() => prop.dicMethod === 'get' ? getAPI : postAPI)
const formatRules = computed(() => {
  const rules = prop.rules || []
  if (!prop.isSearch) rules.push(opt => !getDisable(opt, prop.props) || i18n.t('SelectedTip'))
  return rules
})
const formatPlaceholder = computed(() => isEmpty(selected.value) ? prop.placeholder : '')
const formatDisable = computed(() => {
  return opt => String(values.value || '').split(',').includes(getValue(opt, prop.props)) ? false : getDisable(opt,
    prop.props)
})

const formatFormInfo = value => {
  if (prop.addName && formInfo.value) formInfo.value[`SSS_${prop.field}`] =
    formatLabels(value, prop.props, prop.formatLabel)
}
const getDefaultList = value => {
  let valueList = []
  if (getTypeof(value) === 'number') valueList = [value]
  else if (getTypeof(value) === 'array') valueList = value
  else if (getTypeof(value) === 'string') valueList = String(value).split(',')
  const list = (totalsList.value || []).filter(el => valueList.includes(getValue(el, prop.props)))
  if (list.length === valueList.length) {
    dicData.value = Object.freeze([...totalsList.value])
    selected.value = prop.multiple ? list : list[0]
    formatFormInfo(selected.value)
    return
  }
  if (prop.nonValiValue && !isAjax.value) {
    selected.value = prop.multiple ? valueList : valueList[0]
    formatFormInfo(selected.value)
    return
  }
  if (!isAjax.value) return
  const params = deepClone(prop.dicQuery || {})
  const { key: dynamicKey, value: dynamicValue, isDynamic = true } = prop.dynamicParams || {}
  const searchValues = dynamicValue === 'array' ? valueList : valueList.join(',')
  if (isDynamic) {
    params.andDynamicQueryItems =
      [{ property: dynamicKey || valueKey(prop.props), value: valueList, criterion: 'andIn' }]
  } else params[dynamicKey || valueKey(prop.props)] = searchValues
  params.pageSize = valueList.length
  inLoading.value = true
  API.value(prop.dicUrl, params, data => {
    const dataList = data.rows || data
    totalsList.value = selectUnique([...(prop.options || []), ...dataList], prop.props)
    nextTick(() => {
      dicData.value = Object.freeze([...totalsList.value])
      if (QxSelect.value) QxSelect.value.refresh()
    })
    selected.value = prop.multiple ? selectUnique([...list, ...dataList], prop.props) : dataList[0]
    formatFormInfo(selected.value)
    inLoading.value = false
  }, () => {
    nextTick(() => {
      dicData.value = Object.freeze([...totalsList.value])
      if (QxSelect.value) QxSelect.value.refresh()
    })
    selected.value = prop.multiple ? list : (list[0] || value)
    formatFormInfo(selected.value)
    inLoading.value = false
  })
}

const initData = () => {
  if (values.value === prop.modelValue) return formatFormInfo(selected.value)
  if (isEmpty(prop.modelValue)) {
    selected.value = void 0
    formatFormInfo(selected.value)
    return
  }
  getDefaultList(prop.modelValue)
}

const pageSize = 100
const pages = ref(10)
const pageNum = ref(0)
const totals = ref(10000)
const queryVal = ref(void 0)
const firstIndex = ref(0) // 防止分页时，第一次连续调用两次接口

const initOptions = () => {
  const { dicUrl, options = [] } = prop
  isAjax.value = !!dicUrl
  totalsList.value = options || []
  nextTick(() => {
    pages.value = 10
    pageNum.value = 0
    totals.value = 1000
    firstIndex.value = 0
    dicData.value = [...totalsList.value]
    initData()
  })
}
watch(() => prop.modelValue, () => initData())
watch(() => prop.options, () => initOptions())
watch(() => prop.dicQuery, (newValue, oldValue) => {
  if (!oldValue && !newValue) return
  if (JSON.stringify(newValue) === JSON.stringify(oldValue)) return
  initOptions()
})
watchPostEffect(initOptions)

const ajaxAxios = update => {
  const params = deepClone(prop.dicQuery || {})
  params.isEnable = '1'
  params.pageSize = '-1'
  inLoading.value = true
  API.value(prop.dicUrl, params, data => {
    const list = data.rows || data
    totalsList.value = selectUnique([...(prop.options || []), ...list], prop.props)
    update(() => (dicData.value = Object.freeze([...totalsList.value])), ref => {
      if (ref.options.length) {
        ref.setOptionIndex(-1)
        ref.moveOptionSelection(1, true)
        setTimeout(() => ref.updateMenuPosition(), 50)
      }
    })
    isAjax.value = false
    inLoading.value = false
  }, () => {
    update(() => (dicData.value = Object.freeze([...totalsList.value])), ref => {
      if (ref.options.length) {
        ref.setOptionIndex(-1)
        ref.moveOptionSelection(1, true)
        setTimeout(() => ref.updateMenuPosition(), 50)
      }
    })
    inLoading.value = false
  })
}
const ajaxPage = update => {
  const params = deepClone(prop.dicQuery || {})
  if (queryVal.value) {
    if (prop.keyWords) params[prop.keyWords] = queryVal.value
    else params.andKeywordLikeItem = { keyword: queryVal.value, propertys: labelKey(prop.props) }
  }
  inLoading.value = true
  params.isEnable = '1'
  params.pageSize = pageSize
  params.pageNum = pageNum.value + 1
  API.value(prop.dicUrl, params, data => {
    pageNum.value++
    const list = data.rows || data
    if (!queryVal.value && data.total) {
      totals.value = +data.total
      pages.value = data.pages
    }
    if (pageNum.value === pages.value) isAjax.value = false
    totalsList.value = selectUnique([...(prop.options || []), ...totalsList.value, ...list], prop.props)
    inLoading.value = false
    if (!update) {
      if (queryVal.value) dicData.value = [...list]
      else dicData.value = Object.freeze([...totalsList.value])
      nextTick(() => QxSelect.value && QxSelect.value.refresh())
      return
    }
    update(() => {
      if (queryVal.value) dicData.value = [...list]
      else dicData.value = Object.freeze([...totalsList.value])
    }, ref => {
      if (ref.options.length) {
        ref.setOptionIndex(-1)
        ref.moveOptionSelection(1, true)
        setTimeout(() => ref.updateMenuPosition(), 50)
      }
    })
  }, () => {
    update && update(() => (dicData.value = Object.freeze([...totalsList.value])), ref => {
      if (ref.options.length) {
        ref.setOptionIndex(-1)
        ref.moveOptionSelection(1, true)
        setTimeout(() => ref.updateMenuPosition(), 50)
      }
    })
    inLoading.value = false
  })
}
const onScroll = ({ index }) => {
  if (index < totalsList.value.length - 1) return
  if (!prop.page || inLoading.value || totals.value <= pageSize || pageNum.value > pages.value || !isAjax.value) return
  ajaxPage()
}
const filterFn = (query, update) => {
  if (isAjax.value && pageNum.value < pages.value) {
    if (query || queryVal.value) pageNum.value = 0
    queryVal.value = query
    if (prop.page) {
      debounce.value = 200
      inLoading.value = true
      return ajaxPage(update)
    }
    debounce.value = 50
    return ajaxAxios(update)
  }
  debounce.value = 50
  update(() => (dicData.value = Object.freeze([...filterList(totalsList.value, prop.props, query)])), ref => {
    if (ref.options.length) {
      ref.setOptionIndex(-1)
      ref.moveOptionSelection(0, true)
      setTimeout(() => ref.updateMenuPosition(), 50)
    }
  })
}
const updateSelected = () => {
  emits('update:modelValue', values.value)
  ajaxChange.value = true
}
const clearSelected = () => popupHide()
const popupHide = () => {
  nextTick(() => QxSelect.value && QxSelect.value.blur())
  if (!ajaxChange.value) return
  let list = prop.multiple ? selected.value : [selected.value]
  list = deepClone(list)
  if (prop.change) prop.change(values.value, list)
  else emits('change', values.value, list)
  ajaxChange.value = false
}

const validate = () => QxSelect.value.validate()
defineExpose({ validate })
</script>